﻿using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace Creventive.Wpf.ShellFactory.Workspace.Primitives
{
    /// <summary>
    /// A source for application view containers
    /// </summary>
    public class ApplicationViewContainerSource : ReadOnlyObservableCollection<IApplicationViewContainer>, IDisposable
    {
        private readonly IEnumerable source;

        /// <summary>
        /// Initializes a new instance of the <see cref="ApplicationViewContainerSource"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        public ApplicationViewContainerSource(IEnumerable source)
            : base(new ObservableCollection<IApplicationViewContainer>())
        {
            this.source = source;
            if (this.source is INotifyCollectionChanged)
                ((INotifyCollectionChanged)this.source).CollectionChanged += this.source_CollectionChanged;
            this.Reset();
        }

        private IApplicationViewContainer ToViewContainer(object item)
        {
            if (item is IApplicationViewSource)
                return new SourceContainer((IApplicationViewSource)item);
            else if (item is string)
                return new SourceContainer((string)item);
            else if (item is Uri)
                return new SourceContainer((Uri)item);
            else if (item is ApplicationView)
                return new ViewContainer((ApplicationView)item);
            else
                throw new InvalidOperationException("Unhandled Application View Source");
        }

        /// <summary>
        /// Resets this instance.
        /// </summary>
        public void Reset()
        {
            this.source_CollectionChanged(this.source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        private void source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if ((e.NewItems != null && e.NewItems.Count > 1)
                || (e.OldItems != null && e.OldItems.Count > 1))
                throw new InvalidOperationException(this.GetType().FullName + " does not support batched operations.");

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Reset:
                {
                    this.Items.Clear();
                    foreach (var item in (IEnumerable)sender)
                        this.Items.Add(this.ToViewContainer(item));
                    break;
                }

                case NotifyCollectionChangedAction.Add:
                {
                    var newIndex = e.NewStartingIndex;
                    var item = e.NewItems[0];
                    this.Items.Insert(newIndex, this.ToViewContainer(item));
                    break;
                }

                case NotifyCollectionChangedAction.Move:
                {
                    var newIndex = e.NewStartingIndex;
                    var item = e.NewItems[0];
                    this.Items.Insert(newIndex, this.ToViewContainer(item));
                    break;
                }

                case NotifyCollectionChangedAction.Remove:
                {
                    var oldIndex = e.OldStartingIndex;
                    if (oldIndex < 0 || oldIndex >= this.Count)
                        throw new IndexOutOfRangeException("Tried to remove item which was beyond the collection range (" + oldIndex + ")");
                    this.Items.RemoveAt(oldIndex);
                    break;
                }

                case NotifyCollectionChangedAction.Replace:
                {
                    var oldIndex = e.OldStartingIndex;
                    var newIndex = e.NewStartingIndex;
                    if (oldIndex < 0 || oldIndex >= this.Count)
                        throw new IndexOutOfRangeException("Tried to move item which was beyond the collection range (" + oldIndex + ")");
                    if (newIndex < 0 || newIndex >= this.Count)
                        throw new IndexOutOfRangeException("Tried to move item which was beyond the collection range (" + newIndex + ")");
                    ((ObservableCollection<IApplicationViewContainer>)this.Items).Move(oldIndex, newIndex);
                    break;
                }

                default:
                    throw new IndexOutOfRangeException("Action");
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (this.source is INotifyCollectionChanged)
                ((INotifyCollectionChanged)this.source).CollectionChanged -= this.source_CollectionChanged;
        }

        #region Nested type: SourceContainer

        private class SourceContainer : IApplicationViewContainer
        {
            private readonly IApplicationViewSource source;
            private ApplicationView view;

            public SourceContainer(string source)
            {
                this.source = new ApplicationViewSource(new Uri(source, UriKind.RelativeOrAbsolute));
            }

            public SourceContainer(Uri source)
            {
                this.source = new ApplicationViewSource(source);
            }

            public SourceContainer(IApplicationViewSource source)
            {
                this.source = source;
            }

            public ApplicationView View
            {
                get
                {
                    if (this.view == null)
                        this.view = this.source.Load();
                    return this.view;
                }
            }
        }

        #endregion

        #region Nested type: ViewContainer

        private class ViewContainer : IApplicationViewContainer
        {
            private readonly ApplicationView view;

            public ViewContainer(ApplicationView view)
            {
                this.view = view;
            }

            public ApplicationView View
            {
                get { return this.view; }
            }
        }

        #endregion
    }
}